package com.leyou.search.service;

import com.fasterxml.jackson.core.type.TypeReference;
import com.leyou.common.enums.ExceptionEnum;
import com.leyou.common.exceptions.LyException;
import com.leyou.common.utils.BeanHelper;
import com.leyou.common.utils.JsonUtils;
import com.leyou.common.vo.PageResult;
import com.leyou.item.client.ItemClient;
import com.leyou.item.dto.*;
import com.leyou.search.dto.GoodsDTO;
import com.leyou.search.dto.SearchRequest;
import com.leyou.search.pojo.Goods;
import com.leyou.search.repository.GoodsRepository;
import org.apache.commons.lang3.StringUtils;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.Operator;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.bucket.terms.StringTerms;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.elasticsearch.core.ElasticsearchTemplate;
import org.springframework.data.elasticsearch.core.aggregation.AggregatedPage;
import org.springframework.data.elasticsearch.core.query.FetchSourceFilter;
import org.springframework.data.elasticsearch.core.query.FetchSourceFilterBuilder;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.stream.Collectors;

/**
 * @author Eric
 * @version v1.0
 * @package com.leyou.search.service
 * @date 2019/6/30 19:06
 * @description:
 **/
@Service
public class SearchService {

    @Autowired
    private ItemClient itemClient;

    @Autowired
    private ElasticsearchTemplate elasticsearchTemplate;


    public Goods buildGoods(SpuDTO spuDTO) {

        Goods goods = new Goods();

        // TODO 1.查询用来搜索的字段，包含spu的name、分类名、品牌名
        //获取spuName
        String name = spuDTO.getName();
        //获取分类名
        List<CategoryDTO> categoryDTOList = itemClient.queryCategoryByIds(spuDTO.getCategoryIds());
        String categoryName = categoryDTOList.stream().map(CategoryDTO::getName).collect(Collectors.joining(" "));
        //获取品牌名
        BrandDTO brandDTO = itemClient.queryBrandById(spuDTO.getBrandId());

        String all = name + categoryName + brandDTO.getName();

        // TODO 2.查询spu下sku的集合，这里是String类型
        List<SkuDTO> skuList = itemClient.querySkuBySpuId(spuDTO.getId());
        List<Map<String, Object>> skuMap = new ArrayList<>();
        for (SkuDTO skuDTO : skuList) {
            Map<String, Object> sku = new HashMap<>();
            sku.put("id", skuDTO.getId());
            sku.put("title", skuDTO.getTitle());
            sku.put("price", skuDTO.getPrice());
            sku.put("image", StringUtils.substringBefore(skuDTO.getImages(), ","));
            skuMap.add(sku);
        }

        // TODO 3.查询spu下sku的价格集合，用于排序
        Set<Long> prices = skuList.stream().map(SkuDTO::getPrice).collect(Collectors.toSet());

        // TODO 4.查询规格参数，用来过滤数据的,这里面包含key和值两个部分
        //获取规格参数的key
        List<SpecParamDTO> specParams = itemClient.querySpecParams(null, spuDTO.getCid3(), true);

        //key的值分为普通属性值和特有属性值
        SpuDetailDTO spuDetailDTO = itemClient.querySpuDetailById(spuDTO.getId());
        //获取普通属性值
        Map<Long, String> genericSpec = JsonUtils.toMap(spuDetailDTO.getGenericSpec(), Long.class, String.class);
        //获取特有属性值
        Map<Long, List<String>> specialSpec = JsonUtils.nativeRead(spuDetailDTO.getSpecialSpec(), new TypeReference<Map<Long, List<String>>>() {
        });

        //创建一个Map集合放specParam的key和value
        Map<String, Object> specParamsMap = new HashMap<>();
        //遍历specParams准备赋值
        for (SpecParamDTO specParam : specParams) {
            //判断属性是否是普通属性
            if (specParam.getGeneric()) {
                specParamsMap.put(specParam.getName(), specParam.getNumeric() ? chooseSegment(genericSpec.get(specParam.getId()), specParam) : genericSpec.get(specParam.getId()));
            } else {
                specParamsMap.put(specParam.getName(), specParam.getNumeric() ? chooseSegment(specialSpec.get(specParam.getId()), specParam) : specialSpec.get(specParam.getId()));
            }
        }

        goods.setSubTitle(spuDTO.getSubTitle());
        goods.setSpecs(specParamsMap);
        goods.setSkus(JsonUtils.toString(skuMap));
        goods.setPrice(prices);
        goods.setId(spuDTO.getId());
        //获取更新时间，用于排序
        goods.setCreateTime(spuDTO.getCreateTime().getTime());
        goods.setCategoryId(spuDTO.getCid3());
        goods.setBrandId(spuDTO.getBrandId());
        goods.setAll(all);

        return goods;
    }


    /**
     * 将数值类型的具体规格参数转化为以区间段的形式
     *
     * @param value
     * @param p
     * @return
     */
    private String chooseSegment(Object value, SpecParamDTO p) {
        if (value == null || StringUtils.isBlank(value.toString())) {
            return "其它";
        }
        double val = parseDouble(value.toString());
        String result = "其它";
        // 保存数值段
        for (String segment : p.getSegments().split(",")) {
            String[] segs = segment.split("-");
            // 获取数值范围
            double begin = parseDouble(segs[0]);
            double end = Double.MAX_VALUE;
            if (segs.length == 2) {
                end = parseDouble(segs[1]);
            }
            // 判断是否在范围内
            if (val >= begin && val < end) {
                if (segs.length == 1) {
                    result = segs[0] + p.getUnit() + "以上";
                } else if (begin == 0) {
                    result = segs[1] + p.getUnit() + "以下";
                } else {
                    result = segment + p.getUnit();
                }
                break;
            }
        }
        return result;
    }

    private double parseDouble(String str) {
        try {
            return Double.parseDouble(str);
        } catch (Exception e) {
            return 0;
        }
    }

    /**
     * 根据request，分页查询搜索栏搜索的数据
     *
     * @param request
     * @return
     */
    public PageResult<GoodsDTO> queryGoodsBykey(SearchRequest request) {
        //0. 健壮性判断
        if (request.getKey() == null) {
            throw new LyException(ExceptionEnum.INVALID_PARAM_ERROR);
        }

        //1. 创建原生搜索构造器
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();

        //2. 组织条件
        //2.1 source过滤，控制字段的数量
        queryBuilder.withSourceFilter(new FetchSourceFilter(new String[]{"id", "subTitle", "skus"}, null));


        //2.2 添加搜索条件
        queryBuilder.withQuery(buildBasicQuery(request));
        //2.3 分页条件的添加
        queryBuilder.withPageable(PageRequest.of(request.getPage() - 1, request.getSize()));
        //3.0 执行搜索
        AggregatedPage<Goods> result = elasticsearchTemplate.queryForPage(queryBuilder.build(), Goods.class);

        //4.0 解析结果
        //4.1 获取总记录数
        long total = result.getTotalElements();
        //4.2 获取总页数
        int totalPages = result.getTotalPages();
        //4.3 获取当前页的数据
        List<Goods> goodsList = result.getContent();

        //5.0 转化DTO
        List<GoodsDTO> goodsDTOS = BeanHelper.copyWithCollection(goodsList, GoodsDTO.class);

        return new PageResult<>(total, totalPages, goodsDTOS);

    }

    /**
     * 将基本的查询方法抽取出来
     *
     * @param request
     * @return
     */
    private QueryBuilder buildBasicQuery(SearchRequest request) {

        //构建布尔查询
        BoolQueryBuilder queryBuilder = QueryBuilders.boolQuery();
        //添加基本的match查询
        queryBuilder.must(QueryBuilders.matchQuery("all", request.getKey()).operator(Operator.AND));
        //添加过滤条件
        Map<String, String> filter = request.getFilter();
        for (Map.Entry<String, String> entry : filter.entrySet()) {
            //获取过滤项的名称
            String name = entry.getKey();

            //处理过滤项的名称，由于在索引库中品牌的key实际是brandId
            if ("品牌".equals(name)) {
                name = "brandId";
            } else if ("分类".equals(name)) {
                name = "categoryId";
            } else {
                name = "specs." + name;
            }
            //获取过滤项的值
            String value = entry.getValue();
            //添加过滤条件
            queryBuilder.filter(QueryBuilders.termQuery(name, value));

        }


        return queryBuilder;
    }


    /**
     * 商品分类与品牌的聚合
     *
     * @param request
     * @return
     */
    public Map<String, List<?>> queryByFilter(SearchRequest request) {

        //1. 创建接受商品分类与品牌聚合的Map

        Map<String, List<?>> filterList = new LinkedHashMap<>();

        //2. 构建原生queryBuilder对象

        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();

        //3. 添加查询条件
        QueryBuilder basicQuery = buildBasicQuery(request);
        queryBuilder.withQuery(basicQuery);
        //3.1 添加分页信息
        queryBuilder.withPageable(PageRequest.of(0, 1));
        //3.2 添加显示结果信息
        queryBuilder.withSourceFilter(new FetchSourceFilterBuilder().build());
        //3.3 添加聚合信息
        String brandAgg = "brandAgg";
        String categoryAgg = "categoryAgg";
        queryBuilder.addAggregation(AggregationBuilders.terms(brandAgg).field("brandId"));
        queryBuilder.addAggregation(AggregationBuilders.terms(categoryAgg).field("categoryId"));

        //6. 执行查询语句
        AggregatedPage result = elasticsearchTemplate.queryForPage(queryBuilder.build(), Goods.class);

        //7. 解析结果数据
        //7.1 获取总的聚合结果
        Aggregations aggregations = result.getAggregations();

        //7.2 根据聚合名称获取聚合对象
        Terms bTerms = aggregations.get(brandAgg);
        Terms cTerms = aggregations.get(categoryAgg);

        //7.4 调用handlerCategoryAgg方法获取品牌Map的key和值
        List<Long> list = handlerCategoryAgg(cTerms, filterList);

        //7.3 调用handlerBrandAgg方法获取品牌Map的key和值
        handlerBrandAgg(bTerms, filterList);


        //7.5 处理规格参数的聚合
        if (list != null && list.size() == 1) {
            handlerSpecParams(list.get(0), basicQuery, filterList);
        }

        //返回商品分类与品牌聚合的Map
        return filterList;
    }

    /**
     * 处理规格参数的聚合
     *
     * @param cid
     * @param basicQuery
     * @param filterList
     */
    private void handlerSpecParams(Long cid, QueryBuilder basicQuery, Map<String, List<?>> filterList) {

        //1.0 查询cid下的所有符合条件的规格参数
        List<SpecParamDTO> specParamDTOS = itemClient.querySpecParams(null, cid, true);
        //2.0 添加搜索条件
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
        queryBuilder.withQuery(basicQuery);

        //2.1 分页
        queryBuilder.withPageable(PageRequest.of(0, 1));

        //2.2 显示字段

        queryBuilder.withSourceFilter(new FetchSourceFilterBuilder().build());
        //2.3 对每个规格参数进行聚合

        for (SpecParamDTO specParamDTO : specParamDTOS) {
            String name = specParamDTO.getName();
            queryBuilder.addAggregation(AggregationBuilders.terms(name).field("specs." + name));
        }
        //3.0 执行查询
        AggregatedPage<Goods> aggs = elasticsearchTemplate.queryForPage(queryBuilder.build(), Goods.class);
        Aggregations aggregations = aggs.getAggregations();
        //4.0 解析查询结果
        for (SpecParamDTO specParamDTO : specParamDTOS) {
            String name = specParamDTO.getName();
            StringTerms terms = aggregations.get(name);
            List<String> paramValue = terms.getBuckets().stream()
                    .map(StringTerms.Bucket::getKeyAsString)
                    .filter(StringUtils::isNotEmpty)
                    .collect(Collectors.toList());
            filterList.put(name, paramValue);
        }


    }


    /**
     * 处理分类的聚合
     *
     * @param cTerms
     * @param filterList
     * @return
     */
    private List<Long> handlerCategoryAgg(Terms cTerms, Map<String, List<?>> filterList) {
        List<Long> list = cTerms.getBuckets().stream().map(Terms.Bucket::getKeyAsNumber).map(Number::longValue).collect(Collectors.toList());
        List<CategoryDTO> categoryDTOS = itemClient.queryCategoryByIds(list);
        filterList.put("分类", categoryDTOS);
        return list;
    }

    /**
     * 处理品牌的聚合
     *
     * @param bTerms
     * @param filterList
     */
    private void handlerBrandAgg(Terms bTerms, Map<String, List<?>> filterList) {
        //获取桶的集合，集合进入流中利用方法引用，将桶集合结果映射为桶的key值的集合
        List<Long> list = bTerms.getBuckets().stream().map(Terms.Bucket::getKeyAsNumber).map(Number::longValue).collect(Collectors.toList());
        List<BrandDTO> brandDTOS = itemClient.queryBrandByIds(list);
        filterList.put("品牌", brandDTOS);

    }


    @Autowired
    private GoodsRepository goodsRepository;

    public void createIndex(Long id) {

        SpuDTO spu = itemClient.querySpuBySpuId(id);

        Goods goods = buildGoods(spu);
        goodsRepository.save(goods);

    }


    public void deleteIndex(Long id) {

        goodsRepository.deleteById(id);

    }
}
